/*******************************************************************************
 * This file is part of Crunch Graphics.
 *
 * Crunch Graphics is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * Crunch Graphics is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * Crunch Graphics.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.crunch.graphics.gpu;

import javax.media.opengl.GL;
import javax.media.opengl.GL3;

/** Encapsulates an attribute on the GPU.
 */
public class GpuAttribute extends ReferenceCountedGpuObject {
	/** Sets whether this attribute is enabled.
	 */
	public void setEnabled(boolean e) {
		if (e && !attributeEnabled) {
			getGpuState().getGl().glEnableVertexAttribArray(index);
			attributeEnabled = true;
		} else if (!e && attributeEnabled) {
			getGpuState().getGl().glDisableVertexAttribArray(index);
			attributeEnabled = false;
		}
	}

	/** Returns whether this attribute is enabled.
	 */
	public boolean isEnabled() {
		return attributeEnabled;
	}

	/** Sets the attribute pointer using the currently bounding array buffer.
	 *
	 *  Returns true on success. This method will fail if no array buffer is bound.
	 */
	public boolean setPointer(int size, int type, boolean normalized, boolean integer, int stride, long pointer) {
		GpuBuffer buffer = getGpuState().getBoundBuffer(GL.GL_ARRAY_BUFFER);
		if (buffer == null) {
			// can't set pointer - no buffer bound
			return false;
		}
		setPointer(buffer, size, type, normalized, integer, stride, pointer);
		return true;
	}

	/** Sets the attribute pointer using the given array buffer.
	 */
	public boolean setPointer(GpuBuffer buffer, int size, int type, boolean normalized, boolean integer, int stride, long pointer) {
		if (attributeBuffer == buffer &&
				attributeSize == size &&
				attributeType == type &&
				attributeInteger == integer &&
				(attributeInteger || attributeNormalized == normalized) && // only compare normalization if NOT an integer
				attributeStride == stride &&
				attributePointer == pointer)
			// already set
			return true;

		if (integer) {
			if (type != GL.GL_BYTE && type != GL.GL_UNSIGNED_BYTE &&
					type != GL.GL_SHORT && type != GL.GL_UNSIGNED_SHORT &&
					type != GL3.GL_INT && type != GL.GL_UNSIGNED_INT)
				// not a valid type for IPointer
				return false;
			getGpuState().bindBuffer(GL.GL_ARRAY_BUFFER, buffer);
			getGpuState().getGl().glVertexAttribIPointer(index, size, type, stride, pointer);
		} else {
			getGpuState().bindBuffer(GL.GL_ARRAY_BUFFER, buffer);
			getGpuState().getGl().glVertexAttribPointer(index, size, type, normalized, stride, pointer);
		}
		if (buffer != null) {
			buffer.acquire();
		}
		if (attributeBuffer != null) {
			attributeBuffer.release();
		}
		attributeBuffer = buffer;

		attributeSize = size;
		attributeType = type;
		attributeNormalized = normalized;
		attributeInteger = integer;
		attributeStride = stride;
		attributePointer = pointer;
		return true;
	}

	/** Clears the attribute pointer.
	 */
	public void clearPointer() {
		if (attributeBuffer == null) {
			// already unset
			return;
		}

		// keep same pointer, but "forget" the buffer
		attributeBuffer.release();
		attributeBuffer = null;
	}

	GpuAttribute(GpuState state, int i) {
		super(state);
		index = i;
		int[] iBuf = new int[1];

		getGpuState().getGl().glGetVertexAttribiv(index, GL3.GL_VERTEX_ATTRIB_ARRAY_ENABLED, iBuf, 0);
		attributeEnabled = iBuf[0] != 0;

		getGpuState().getGl().glGetVertexAttribiv(index, GL3.GL_VERTEX_ATTRIB_ARRAY_SIZE, iBuf, 0);
		attributeSize = iBuf[0];

		getGpuState().getGl().glGetVertexAttribiv(index, GL3.GL_VERTEX_ATTRIB_ARRAY_TYPE, iBuf, 0);
		attributeType = iBuf[0];

		getGpuState().getGl().glGetVertexAttribiv(index, GL3.GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, iBuf, 0);
		attributeNormalized = iBuf[0] != 0;

		attributeInteger = false;

		getGpuState().getGl().glGetVertexAttribiv(index, GL3.GL_VERTEX_ATTRIB_ARRAY_STRIDE, iBuf, 0);
		attributeStride = iBuf[0];

		// function does not exist, so set the initial value to 0, as described in the OpenGL spec
//		long[] lBuf = new long[1];
//		getGpuState().getGl().glGetVertexAttribPointerv(index, GL3.GL_VERTEX_ATTRIB_ARRAY_POINTER, lBuf, 0);
//		attributePointer = lBuf[0];
		attributePointer = 0;

	}

	protected void onRelease() {
		if (attributeBuffer != null) {
			attributeBuffer.release();
		}
	}

	private int index;
	private boolean attributeEnabled;
	private GpuBuffer attributeBuffer;
	private int attributeSize;
	private int attributeType;
	private boolean attributeNormalized;
	private boolean attributeInteger;
	private int attributeStride;
	long attributePointer;
}